package com.rengwuxian.coursecoroutines._3_scope_context

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlin.coroutines.EmptyCoroutineContext

/**
 * coroutineScope() 和 supervisorScope()
 * coroutineScope()、supervisorScope()、launch()、CoroutineScope、SupervisorJob
 *
 * scope.launch {
 *     coroutineScope {}
 *     supervisorScope {} //SupervisorJob
 * }
 *
 * coroutineScope {
 * }
 * 会沿用父协程的coroutineContext，会创建一个子Job，和launch很相似。
 * 区别：1.coroutineScope {} 是无法定制coroutineContext的，无法加入定制化参数。不像launch（Dispatchers.IO + Job()）可以定制。
 *      2.coroutineScope {} 是一个挂起函数，串行的，等待内部执行结束，才返回。（而launch启动的协程是并行的）
 *                          内部如果再开启子协程，也要等子协程都运行完 --》父子协程的工作机制（类似withContext()）
 *      3.应该说非常像launch之后的join
 *      4.coroutineScope {} 函数是有返回值的，最后一行代码（直接拿到），而launch是没有返回值的，从这个角度来看更像async
 * 为什么跟launch对比，是因为效果比较像，但是应用场景是完全不同的，没有人会觉得不用launch去用coroutineScope{}更好。
 * 什么都不用的效果，跟coroutineScope{}就是一模一样的了。
 *
 * 设想挂起函数中，如果想用launch，但是没有CoroutineScope，怎么办？
 * private suspend fun someFun() = coroutineScope {
 *   launch {
 *   }
 * }
 * 用coroutineScope{}包裹一下。coroutineScope{}可以提供CoroutineScope类型的this上下文。用来启动协程。
 *
 * 挂起函数都是串行的，等待返回后，再执行后面代码。
 * 常用场景：
 * 1.用来在挂起函数中提供一个CoroutineScope的上下文，挂起函数中启动一个协程的场景
 * 2.业务逻辑串行封装（最后一行是返回值），先并行，后拿返回值。从这个角度看，更像async
 * coroutineScope {
 *         val deferred1 = async { "rengwuxian" }
 *         val deferred2: Deferred<String> = async { throw RuntimeException("Error!") }
 *         "${deferred1.await()} ${deferred2.await()}"
 *       }
 * 3.在业务封装逻辑使用中，在一大块协程代码里，把一小块具备独立功能的协程代码包起来，让这块代码在遇到异常的时候，可以正常的被try/catch，而不是毁灭整个协程树。
 *   可以抓住内部并行协程的异常（协程与协程之间的异常无法感知（包括父子协程之间），并行的），在前面async并行那一小节有提及。
 *   但是coroutineScope是一个挂起函数，外部协程是挂起的，所以它的内部出现了任何异常，外部协程是可以抓住的。
 *     val name = try {
 *       coroutineScope {
 *         val deferred1 = async { "rengwuxian" }
 *         val deferred2: Deferred<String> = async { throw RuntimeException("Error!") }
 *         "${deferred1.await()} ${deferred2.await()}"
 *       }
 *     } catch (e: Exception) {
 *       e.message
 *     }
 *
 *
 *     supervisorScope {
 *     }
 *     它的作用和coroutineScope几乎一样，就是里面提供的Job是SupervisorJob，适用场景不多。
 *
 * 本例子代码log输出如下：
 * Full name: Error!
 * Duration within coroutineScope: 1021
 * Duration of coroutineScope: 2003
 * Duration of launch: 0
 * Duration within launch: 1002
 */
fun main() = runBlocking<Unit> {
  val scope = CoroutineScope(EmptyCoroutineContext)
  scope.launch {
    supervisorScope {

    }
    val name = try {
      coroutineScope {
        val deferred1 = async { "rengwuxian" }
        val deferred2: Deferred<String> = async { throw RuntimeException("Error!") }
        "${deferred1.await()} ${deferred2.await()}"
      }
    } catch (e: Exception) {
      e.message
    }
    println("Full name: $name")
    val startTime1 = System.currentTimeMillis()
    coroutineScope {
      launch {
        delay(2000)
      }
      delay(1000)
      println("Duration within coroutineScope: ${System.currentTimeMillis() - startTime1}")
    }
    println("Duration of coroutineScope: ${System.currentTimeMillis() - startTime1}")
    val startTime = System.currentTimeMillis()
    launch {
      delay(1000)
      println("Duration within launch: ${System.currentTimeMillis() - startTime}")
    }
    println("Duration of launch: ${System.currentTimeMillis() - startTime}")
  }
  delay(10000)
}

private suspend fun someFun() = coroutineScope {
  launch {

  }
}